package jmine.tec.rpc.server.provider;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import jmine.tec.rpc.RPCMessages;
import jmine.tec.rpc.common.RPCException;
import jmine.tec.rpc.common.entity.EntityRepresentation;
import jmine.tec.rpc.common.entity.EntityRest;
import jmine.tec.rpc.common.metadata.RestServiceMetadata;
import jmine.tec.rpc.common.provider.RestServiceProvider;

/**
 * Provedor de serviços REST, lado servidor. Permite registro direto de serviços.
 * 
 * @author lundberg
 */
public class ServerRestServiceProvider implements RestServiceProvider {

    private final Map<Class<?>, Object> implementors = new ConcurrentHashMap<Class<?>, Object>();

    private final Map<Class<?>, EntityRest<?>> entityImplementors = new ConcurrentHashMap<Class<?>, EntityRest<?>>();

    private final Map<Class<?>, Class<?>> entityServices = new ConcurrentHashMap<Class<?>, Class<?>>();

    /**
     * {@inheritDoc}
     */
    public <S> S provideByClass(Class<S> service) {
        if (this.implementors.containsKey(service)) {
            return service.cast(this.implementors.get(service));
        } else {
            throw new RPCException(RPCMessages.SERVICE_NOT_FOUND.create(service.getName()));
        }
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public <R extends EntityRepresentation> EntityRest<R> provideByRepresentation(Class<R> representation) {
        if (this.providesByRepresentation(representation)) {
            return (EntityRest<R>) this.entityImplementors.get(representation);
        } else {
            throw new RPCException(RPCMessages.ENTITY_REST_SERVICE_NOT_FOUND.create(representation.getName()));
        }
    }

    /**
     * {@inheritDoc}
     */
    public <R extends EntityRepresentation> boolean providesByRepresentation(Class<R> representation) {
        return this.entityImplementors.containsKey(representation);
    }

    /**
     * Verifica se existe serviço implementação do serviço
     * 
     * @param <S> tipo de serviço
     * @param service classe do serviço
     * @return boolean
     */
    public <S> boolean provides(Class<S> service) {
        return this.implementors.containsKey(service);
    }

    /**
     * Busca a interface que provê acesso à representação
     * 
     * @param <R> tipo de representação
     * @param representation representation
     * @return Class
     */
    @SuppressWarnings("unchecked")
    public <R extends EntityRepresentation> Class<? extends EntityRest<R>> getRepresentationService(Class<R> representation) {
        return (Class<? extends EntityRest<R>>) this.entityServices.get(representation);
    }

    /**
     * {@inheritDoc}
     */
    public void register(Class<?> service, Object implementor) {
        RestServiceMetadata.verifyService(service);
        if (!service.isInstance(implementor)) {
            throw new RPCException(RPCMessages.IMPLEMENTOR_DOES_NOT_IMPLEMENT_GIVEN_SERVICE.create(implementor, service.getName()));
        }
        this.implementors.put(service, implementor);
        if (EntityRest.class.isAssignableFrom(service)) {
            EntityRest<?> entityImplementor = (EntityRest) implementor;
            this.entityImplementors.put(entityImplementor.getRepresentationClass(), entityImplementor);
            this.entityServices.put(entityImplementor.getRepresentationClass(), service);
        }
    }
}
